'''
Created on Jan 22, 2013

@author: shinshen
'''


import os
import pickle
import math

import game
import pygame
import pygame.locals

class NavMesh(object):
    def __init__(self):
        self.mesh_rect = []

        self.mesh_overlay = None
        pass

    def generate(self, source_pathgrid):
        pathgrid = source_pathgrid.copy()
        for p in sorted(pathgrid.keys()):
            (x, y) = p
            if p in pathgrid and pathgrid[p]:
                mesh_size = self.expand_tile(p, (1, 1), pathgrid)

                rect_position = (p[0] * 16, p[1] * 16)
                rect_size = (mesh_size[0] * 16, mesh_size[1] * 16)
                rect = pygame.Rect(rect_position, rect_size)
                #rect = pygame.Rect(p, mesh_size)
                self.mesh_rect.append(rect)


    def generate_overlay(self):
        max_x = 0
        max_y = 0

        for r in self.mesh_rect:
            if r.right > max_x: max_x = r.right
            if r.bottom > max_y: max_y = r.bottom

        self.mesh_overlay = pygame.Surface((max_x, max_y), pygame.SRCALPHA, 32)

        for r in self.mesh_rect:
            pygame.draw.rect(self.mesh_overlay, (32, 32, 32), r, 0)
            pygame.draw.rect(self.mesh_overlay, (255, 255, 255), r, 1)


    def expand_tile(self, p, current_size, pathgrid):
        (x, y) = p
        (sx, sy) = current_size

        # Try to expand to bottom first
        expand = True
        scan_x = x + sx
        scan_y = y + sy
        for tx in xrange(x, scan_x):
            print "scanning (%d, %d)" % (tx, scan_y)
            if (tx, scan_y) not in pathgrid or not pathgrid[tx, scan_y]:
                expand = False
                break

        if expand:
            for tx in xrange(x, scan_x):
                del pathgrid[tx, scan_y]

            current_size = (sx, sy + 1)

            current_size = self.expand_tile(p, current_size, pathgrid)

        (sx, sy) = current_size

        # Then try to expand to right
        expand = True
        scan_x = x + sx
        scan_y = y + sy
        for ty in xrange(y, scan_y):
            print "scanning (%d, %d)" % (scan_x, ty)
            if (scan_x, ty) not in pathgrid or not pathgrid[scan_x, ty]:
                expand = False
                break

        if expand:
            for ty in xrange(y, scan_y):
                del pathgrid[scan_x, ty]

            current_size = (sx + 1 , sy)

            current_size = self.expand_tile(p, current_size, pathgrid)

            print "expand y"

        return current_size

class PathViewer(object):
    def __init__(self):
        pygame.init()
        pygame.font.init()
        pygame.display.set_caption('COS')

        screen = pygame.display.set_mode((1024, 768))
        clock = pygame.time.Clock()

        cont = True

        map_path = os.path.join("data", "map")
        game_map = game.Map(os.path.join(map_path, "desert.tmx"))

        path_cache_file = os.path.join(map_path, "desert.path")
        if  os.path.isfile(path_cache_file):
            game_map.pathnode = pickle.load(open(path_cache_file, "r"))
            game_map.generate_path_overlay()
        else:
            game_map.generate_pathnode()
            pickle.dump(game_map.pathnode, open(path_cache_file, "w"))

        """
        print game_map.pathnode.list
        print game_map.pathnode.node_value
        print game_map.pathnode.edge_value
        """

        """
        nav_mesh = NavMesh()
        nav_mesh.generate(game_map.pathmap)
        nav_mesh.generate_overlay()
        """

        haa_pathnode = HAAPathNode()
        grid_size = (game_map.tiledmap.width * 2, game_map.tiledmap.height * 2)
        haa_pathnode.generate_clearance(game_map.pathmap, game_map.map_size, grid_size)
        haa_pathnode.generate_clearance_overlay()
        haa_pathnode.generate_pathnode((5, 5))
        haa_pathnode.generate_path_overlay()

        while cont:
            clock.tick(60)
            for e in pygame.event.get():
                if e.type == pygame.QUIT:
                    cont = False

                elif e.type == pygame.KEYDOWN:
                    if e.key == pygame.locals.K_ESCAPE:
                        cont = False

                    if e.key == pygame.locals.K_UP:
                        game_map.focus += (0, 16)

                    if e.key == pygame.locals.K_DOWN:
                        game_map.focus += (0, -16)

                    if e.key == pygame.locals.K_LEFT:
                        game_map.focus += (16, 0)

                    if e.key == pygame.locals.K_RIGHT:
                        game_map.focus += (-16, 0)

            screen.fill((0, 0, 0))
            #screen.blit(nav_mesh.mesh_overlay, (game_map.focus))
            screen.blit(haa_pathnode.path_overlay, (game_map.focus))
            pygame.display.flip()

        pygame.quit()

class HAAPathNode(object):
    def __init__(self):
        self.path_graph = None

        self.clearance = None
        self.clearance_overlay = None

        self.cluster_overlay = None
        self.cluster_dimension = None  # Size of a single cluster count by tiles
        self.cluster_size = None # Total amount of clusters

        self.map_size = None  # Map size in actual pixels
        self.grid_size = None  # Total amount of path tiles for the map
        self.path_size = None  # Dimension of a single path tile

    def generate_clearance(self, pathmap, map_size, grid_size):
        """
        Generate a clearance table based on path-map provided by map.
        This data is a must have in order to generate the actual
        HAA path node

        Note that clearance table is grid/tile based without real
        world coordinates, hence additional data is required
        """
        clearance = {}

        self.map_size = map_size
        self.grid_size = grid_size
        size_x = map_size[0] / grid_size[0]
        size_y = map_size[1] / grid_size[1]
        self.path_size = (size_x, size_y)

        for y in xrange(0, grid_size[0]):
            for x in xrange(0, grid_size[1]):
                clearance[x, y] = self.calculate_tile_clearance(pathmap, x, y)

        self.clearance = clearance


    def calculate_tile_clearance(self, pathmap, x, y):
        if pathmap[x, y] == 0: return 0
        clearance = 0
        while True:
            for tx in xrange(x, x + clearance + 1):
                for ty in xrange(y, y + clearance + 1):
                    if (tx, ty) not in pathmap:
                        return clearance

                    if pathmap[tx, ty] == 0:
                        return clearance

            clearance += 1
        return clearance

    def generate_clearance_overlay(self):
        (pw, ph) = self.path_size

        font = pygame.font.Font(pygame.font.get_default_font(), 12)

        sf_pass = pygame.Surface(self.path_size, pygame.SRCALPHA, 32)
        sf_pass.fill(pygame.Color(64, 64, 64))

        sf_blocked = pygame.Surface(self.path_size, pygame.SRCALPHA, 32)
        sf_blocked.fill(pygame.Color(0, 0, 0))

        self.clearance_overlay = pygame.Surface(self.map_size, pygame.SRCALPHA, 32)

        for (x, y) in self.clearance:
            c = self.clearance[x, y]

            if c > 0:
                self.clearance_overlay.blit(sf_pass, (x * pw, y * ph))
            else:
                self.clearance_overlay.blit(sf_blocked, (x * pw, y * ph))

            clearance_surface = font.render(str(c), 1, (255, 255, 255))
            self.clearance_overlay.blit(clearance_surface, (x * pw, y * ph))

        return self.clearance_overlay

    def get_clearance(self, key):
        if key in self.clearance:
            return self.clearance[key]
        else:
            return 0

    def generate_pathnode(self, cluster_dimension):
        # TODO: Remove all drawing function from this function
        (px, py) = self.cluster_dimension = cluster_dimension

        cw = int(math.ceil(self.grid_size[0] / px))
        ch = int(math.ceil(self.grid_size[1] / py))
        
        self.cluster_size = (cw, ch)

        self.pathnode = game.PathGraph()
        self.path_overlay = pygame.Surface(self.map_size, pygame.SRCALPHA, 32)

        self.entrance_point = {}
        self.cluster_pathnode = {}

        entrance_point = self.entrance_point
        cluster_pathnode = self.cluster_pathnode

        for x in xrange(0, cw):
            for y in xrange(0, ch):
                entrance_point[x, y] = []

                cluster_pathnode[x, y] = {}

        for x in xrange(0, cw):
            for y in xrange(0, ch):
                # topright = ((x + 1) * cd, y * cd)
                # bottomright = ((x + 1) * cd, (y + 1) * cd)
                # bottomleft = ((x) * cd, (y + 1) * cd)

                # pygame.draw.line(self.path_overlay, (64,0,0), topright, bottomright, 4)
                # pygame.draw.line(self.path_overlay, (64,0,0), bottomleft, bottomright, 4)

                start_x = x * px
                end_x = (x + 1) * px
                start_y = y * py
                end_y = (y + 1) * py

                begin_x = None
                begin_y = None

                for xx in xrange(start_x, end_x):
                    for yy in xrange(start_y, end_y):
                        cluster_pathnode[x, y][xx, yy] = self.get_clearance((xx, yy))

                for xx in xrange(start_x, end_x):
                    #self.draw_clearance_tile(xx, end_y - 1)
                    #self.draw_clearance_tile(xx, end_y)

                    clearance_from = self.get_clearance((xx, end_y - 1))
                    clearance_to = self.get_clearance((xx, end_y))

                    if begin_x != None:
                        if clearance_from < 2 or clearance_to < 2:
                            begin = (begin_x, end_y - 1)
                            end = (xx - 1, end_y - 1)
                            e1 = self.insert_entrance_point(begin, end, entrance_point[x, y])

                            begin = (begin_x, end_y)
                            end = (xx - 1, end_y)
                            e2 = self.insert_entrance_point(begin, end, entrance_point[x, y + 1])
                            begin_x = None

                            self.add_pathnode_edge(e1, e2)

                    else:
                        if clearance_from >= 2 and clearance_to >= 2:
                            begin_x = xx

                if begin_x != None:
                    begin = (begin_x, end_y - 1)
                    end = (xx, end_y - 1)
                    e1 = self.insert_entrance_point(begin, end, entrance_point[x, y])

                    begin = (begin_x, end_y)
                    end = (xx, end_y)
                    e2 = self.insert_entrance_point(begin, end, entrance_point[x, y + 1])
                    begin_x = None

                    self.add_pathnode_edge(e1, e2)

                for yy in xrange(start_y, end_y):
                    #self.draw_clearance_tile(end_x - 1, yy)
                    #self.draw_clearance_tile(end_x, yy)

                    clearance_from = self.get_clearance((end_x - 1, yy))
                    clearance_to = self.get_clearance((end_x, yy))

                    if begin_y != None:
                        if clearance_from < 2 or clearance_to < 2:
                            begin = (end_x - 1, begin_y)
                            end = (end_x - 1, yy - 1)
                            e1 = self.insert_entrance_point(begin, end, entrance_point[x, y])

                            begin = (end_x, begin_y)
                            end = (end_x, yy - 1)
                            e2 = self.insert_entrance_point(begin, end, entrance_point[x + 1, y])
                            begin_y = None

                            self.add_pathnode_edge(e1, e2)

                    else:
                        if clearance_from >= 2 and clearance_to >= 2:
                            begin_y = yy

                if begin_y != None:
                    begin = (end_x - 1, begin_y)
                    end = (end_x - 1, yy)
                    e1 = self.insert_entrance_point(begin, end, entrance_point[x, y])

                    begin = (end_x, begin_y)
                    end = (end_x, yy)
                    e2 = self.insert_entrance_point(begin, end, entrance_point[x + 1, y])
                    begin_y = None

                    self.add_pathnode_edge(e1, e2)

                for e in entrance_point[x, y][:]:
                    for e2 in entrance_point[x, y][:]:
                        if e == e2:
                            continue;

                        path = game.Pathfinder.a_star(e, e2, cluster_pathnode[x, y])

                        if path == False:
                            continue

                        self.pathnode.add(e, e2)
                        self.pathnode.set_edge_value(e, e2, path)

    def add_pathnode_edge(self, e1, e2):
        self.pathnode.add(e1, e2)
        self.pathnode.set_edge_value(e1, e2, [e1, e2])
        self.pathnode.add(e2, e1)
        self.pathnode.set_edge_value(e2, e1, [e2, e1])

    def insert_entrance_point(self, begin, end, saved_list):
        mid_x = int((begin[0] + end[0]) / 2)
        mid_y = int((begin[1] + end[1]) / 2)
        mid = (mid_x, mid_y)

        saved_list.append((mid_x, mid_y))
        return mid

    def generate_path_overlay(self):
        path_overlay = pygame.Surface(self.map_size, pygame.SRCALPHA, 32)

        encpath = pygame.Surface((16, 16), pygame.SRCALPHA, 32)
        encpath.fill(pygame.Color(0, 255, 0, 192))

        for node in self.pathnode.list:
            neighbors = self.pathnode.neighbors(node)
            e1 = (node[0] * 16, node[1] * 16)

            path_overlay.blit(encpath, e1)

            for n in neighbors:
                path = self.pathnode.get_edge_value(node, n)
                if path:
                    worldpath = []

                    for p in path:
                        p = (p[0] * 16, p[1] * 16)
                        worldpath.append(p)

                    pygame.draw.lines(path_overlay, (255, 255, 0), False, worldpath, 2)

        self.path_overlay = path_overlay

        passpath = pygame.Surface((16, 16), pygame.SRCALPHA, 32)
        passpath.fill(pygame.Color(0, 255, 255, 128))

        badpath = pygame.Surface((16, 16), pygame.SRCALPHA, 32)
        badpath.fill(pygame.Color(128, 0, 0, 128))

        (px, py) = self.cluster_dimension


        for x in xrange(0, self.cluster_size[0]):
            for y in xrange(0, self.cluster_size[1]):
                start_x = x * px
                end_x = (x + 1) * px
                start_y = y * py
                end_y = (y + 1) * py

                for xx in xrange(start_x, end_x):
                    self.draw_clearance_tile(xx, end_y - 1, badpath, passpath)
                    self.draw_clearance_tile(xx, end_y, badpath, passpath)

                for yy in xrange(start_y, end_y):
                    self.draw_clearance_tile(end_x - 1, yy, badpath, passpath)
                    self.draw_clearance_tile(end_x, yy, badpath, passpath)




        return path_overlay


    def draw_clearance_tile(self, x, y, badpath, passpath):
        (pw, ph) = self.path_size
        if self.get_clearance((x, y)) < 2:
            self.path_overlay.blit(badpath, (x * pw, y * ph))
        else:
            self.path_overlay.blit(passpath, (x * pw, y * ph))


if __name__ == '__main__':
    PathViewer()


